#ifdef __USE_CMSIS
#include "LPC17xx.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "find_uart_parameters.h"

float lookuptable(float FRest_calcule, int pclk, int dlm, int dll);
int DIVADDVAL_table[] = {0,1,1,1,1,1,1,1,1,2,1,2,1,2,1,3,2,3,1,4,3,2,3,4,1,5,4,3,5,2,5,3,4,5,6,7,1,8,7,6,5,
4,7,3,8,5,7,9,2,9,7,5,8,11,3,10,7,11,4,9,5,11,6,13,7,8,9,10,11,12,13,14};
int MULVAL_table[] = {1,15,14,13,12,11,10,9,8,15,7,13,6,11,5,14,9,13,4,15,11,7,10,13,3,14,11,8,13,5,12,7,
9,11,13,15,2,15,13,11,9,7,12,5,13,8,11,14,3,13,10,7,11,15,4,13,9,14,5,11,6,13,7,15,
8,9,10,11,12,13,14,15};


// DLL Divisor Latch LSB (Baud rate divisor value LSB)
// DLM Divisor Latch MSB (Baud rate divisor valuse MSB)
// DLL & DLM hold the value used along with the Fractional Divider to divide the APB clock (PCLK)
// in order to produce the baud rate clock WHICH MUST BE 16 x the desired baud rate
// The Divisor Latch Access Bit in LCR must 1 to allow access to the UART Divisor Latches
// FR: Fractional Rate divider
// DL: Divisor Latch: DLL (lsb), DLM (msdb)

// Exemples de valeurq
// ! a la vitesse maxi admissible du MAX232 ~ 120000 baud
// 300:    DIVADDVAL=1, MULVAL=2,  DLM=13, DLL=144 si 100 MHz & PCLK=CCLK/4
// 1200:   DIVADDVAL=1, MULVAL=2,  DLM=3,  DLL=100 si 100 MHz & PCLK=CCLK/4
// 4800:   DIVADDVAL=1, MULVAL=2,  DLM=0,  DLL=217 si 100 MHz & PCLK=CCLK/4
// 9600:   DIVADDVAL=1, MULVAL=2,  DLM=0,  DLL=108 si 100 MHz & PCLK=CCLK/4
// 38400:  DIVADDVAL=1, MULVAL=2,  DLM=0,  DLL=27  si 100 MHZ & PCLK=CCLK/4
// 57600:  DIVADDVAL=1, MULVAL=2,  DLM=0,  DLL=18  si 100 MHZ & PCLK=CCLK/4
// 115200: DIVADDVAL=1, MULVAL=2,  DLM=0,  DLL=9   si 100 MHZ & PCLK=CCLK/4
// 230400: DIVADDVAL=9, MULVAL=13, DLM=0,  DLL=4   si 100 MHZ & PCLK=CCLK/4 ! NG (MAX232 limit ?!)

int DIVADDVAL, MULVAL, DLL, DLM, PCLK;

int set_optimal_uart_config(int BR)  // BR = Baud Rate
{
	find_optimal_uart_config(BR); // met a jour DIVADDVAL, MULVAL, DLL & DLM pour le BR demande
	return set_optimal_uart_config_for_cclk(SystemCoreClock, BR);
}
int set_optimal_uart_config_for_cclk(int sysclock, int BR)
{
	find_optimal_uart_config_for_cclk(sysclock, BR); // met a jour DIVADDVAL, MULVAL, DLL & DLM pour le BR demande
  	LPC_SC->PCONP |= bit4;  // Turn On UART1
    LPC_UART1->LCR |= bit7; // Donne acces au DLAB (Divisor Latch), LCR Line Control Register
    LPC_UART1->DLL = DLL;  // DLL Divisor Latch LSB
	LPC_UART1->DLM = DLM;  // DLM Divisor Latch MSB
	LPC_UART1->FDR = (MULVAL<<4)|DIVADDVAL;  // FDR Fractional Baud Rate Divisor Register
	LPC_UART1->LCR = 0x03;	// 8 bits, no Parity, 1 Stop bit DLAB=0 -> Disable Access to Divisor Latch
	LPC_UART1->FCR |= (bit0|bit1|bit2); // Enable and reset TX and RX FIFO
	// Regle la Clock peripherique, bit 8 & 9 pour UART1
	LPC_SC->PCLKSEL0 &= ~(bit8|bit9);  // RAZ des 2 bit pour les OR du 'switch case' soient effectifs
	switch(PCLK) // PCLK contient la valeur du diviseur: 1, 2, 4 ou 8 -> if faut encoder cete valeur dans les bons bit
	{
		case 1: LPC_SC->PCLKSEL0 |= bit8 ; break;
		case 2: LPC_SC->PCLKSEL0 |= bit9 ; break;
		case 8: LPC_SC->PCLKSEL0 |= (bit8|bit9) ; break;
		default: ; // si PCLK = 4 ou autre(=bug) on laisse les 2 bit a zero => PCLK = CCLK/4
	}
  return 0;
}

int find_optimal_uart_config(int BR) // BR = Baud Rate
{
	SystemCoreClockUpdate();
	return find_optimal_uart_config_for_cclk(SystemCoreClock, BR);
}

int find_optimal_uart_config_for_cclk(int sysclock, int BR)
{
 int iteration, diviseur, pclk; // pclk = clock peripherique
 int plus_proche_DLM, plus_proche_DLL, plus_proche_DIVADDVAL, plus_proche_MULVAL;
 float DLest, FRest;
 float difference_mini = 1000000.0;
 float plus_proche_baudrate;
  // Calcule la valeur du Baud Rate avec les 4 diviseurs (/1,/2,/4,/8) possible et selectionne le BR calcule
  // le plus proche de la valeur demandee
  for(diviseur=1; diviseur <= 8; diviseur=diviseur*2)
  {
    pclk = sysclock/diviseur;
    printf("\nDivisor Latch Calculation for a Baud Rate of %d with a PCLK=CCLK/%d=%d/%d=%d [MHz]\n", BR, diviseur, SystemCoreClock, diviseur, pclk);
    DLest = pclk/(16.0*BR);
    int DLest_int = (int)DLest;

    if ( DLest != DLest_int ) // DLest n'est pas un entier
    {
      //printf("\tDLest (%f) is NOT an integer, iterating...\n", DLest);
      // on demarre avec FRest = 1.5
      FRest = 1.5;
      iteration = 1;
      while( 1 )
      {
          DLest = (int)(pclk/(16.0*BR*FRest));
          DLM = ((int)DLest & 0xff00) >> 8;
          DLL = (int)DLest & 0xff;
          FRest = pclk/(16.0*BR*DLest);
          //printf("\tIteration #%d FRest=%f, DLest=%f\n", iteration++, FRest, DLest);
            float baudrate_reelle  = lookuptable(FRest, pclk, DLM, DLL);  // lookuptable met a jour DIVADDVAL & MULVAL
            float diff = fabsf(baudrate_reelle - BR);
            if ( diff <= difference_mini ) // "<" pour favoriser le diviseur le plus petit, "<=" pour favoriser le diviseur le plus grand
            {
            	difference_mini = diff;
            	plus_proche_baudrate = baudrate_reelle;
            	plus_proche_DLM = DLM;
            	plus_proche_DLL = DLL;
            	PCLK = diviseur;
            	plus_proche_DIVADDVAL = DIVADDVAL;
            	plus_proche_MULVAL = MULVAL;
            }
          if ( (FRest <= 1.1) || (FRest >= 1.9) )
          {
             if ( iteration == 1 )
              FRest =  1.1;
             else
              FRest += 0.1;
             printf("\tNew iteration with FRest = %f\n", FRest);
          }
          else
            break;
          if ( iteration > 10 )
          {
              printf("\tTrop d'iterations !\n");
              break;
          }
      }
    }
    else // DLest est un entier, cas trial
    {
      printf("\tDLest (%d )is an integer\n", DLest_int);
      DIVADDVAL = 0;
      MULVAL = 1;
      printf("\tDIVADDVAL=%d, MULVAL=%d", DIVADDVAL, MULVAL);
    }
  }
	DLM= plus_proche_DLM ;
	DLL = plus_proche_DLL;
	DIVADDVAL = plus_proche_DIVADDVAL;
	MULVAL = plus_proche_MULVAL;
 printf("MEILLEUR CHOIX: %f, DIVADDVAL=%d, MULVAL=%d, DLM=%d, DLL=%d pour un diviseur de %d\n",plus_proche_baudrate, DIVADDVAL, MULVAL, DLM, DLL, PCLK);
return 0;
}

// recherche la valeur la plus proche du FR (Fractional Divisor)
float lookuptable(float FRest_calcule, int pclk, int dlm, int dll)
{
    float difference, minimum = 10.0;
    int i, index_du_minimun = -1;
    float FR;
    float uart_baudrate;
    for(i=0; i < sizeof(DIVADDVAL_table)/sizeof(int); i++)
    {
        FR = 1.0+(float)DIVADDVAL_table[i]/(float)MULVAL_table[i];
        difference = fabsf(FR-FRest_calcule);
        if ( difference < minimum )
        {
            index_du_minimun = i;
            minimum = difference;
        }
    }
    if ( index_du_minimun > -1 )
    {
     uart_baudrate = (float)pclk/(16.0*(256.0*dlm+dll)*(1.0+(float)DIVADDVAL_table[index_du_minimun]/(float)MULVAL_table[index_du_minimun]) );
     DIVADDVAL = DIVADDVAL_table[index_du_minimun];
     MULVAL = MULVAL_table[index_du_minimun];
     printf("-> plus proche valeur de FR trouvee=%f @ index=%d -> DIVADDVAL=%d, MULVAL=%d, DLM=%d, DLL=%d, BR=%f\n", FR, index_du_minimun, DIVADDVAL, MULVAL, DLM, DLL, uart_baudrate);
    }
    else
    {
     printf("! AUCUN FR TROUVEE !\n");
     // TODO Retombe sur 9600,8,N,1
     uart_baudrate = -1.0;
    }
    return uart_baudrate;
}

